/*
 * Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.protocol;

import java.util.Iterator;
import java.util.HashMap;

import javax.rmi.CORBA.Tie;

import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.Context;
import org.omg.CORBA.ContextList;
import org.omg.CORBA.ExceptionList;
import org.omg.CORBA.NamedValue;
import org.omg.CORBA.NVList;
import org.omg.CORBA.Request;
import org.omg.CORBA.TypeCode;

import org.omg.CORBA.portable.ApplicationException;
import org.omg.CORBA.portable.Delegate;
import org.omg.CORBA.portable.InputStream;
import org.omg.CORBA.portable.OutputStream;
import org.omg.CORBA.portable.RemarshalException;
import org.omg.CORBA.portable.ServantObject;

import com.sun.corba.se.pept.broker.Broker;
import com.sun.corba.se.pept.encoding.InputObject;
import com.sun.corba.se.pept.encoding.OutputObject;
import com.sun.corba.se.pept.protocol.ClientInvocationInfo;
import com.sun.corba.se.pept.protocol.ClientRequestDispatcher;
import com.sun.corba.se.pept.transport.ContactInfo;
import com.sun.corba.se.pept.transport.ContactInfoList;
import com.sun.corba.se.pept.transport.ContactInfoListIterator;

import com.sun.corba.se.spi.presentation.rmi.StubAdapter;
import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.logging.CORBALogDomains;
import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.spi.protocol.CorbaClientDelegate;
import com.sun.corba.se.spi.transport.CorbaContactInfo;
import com.sun.corba.se.spi.transport.CorbaContactInfoList;
import com.sun.corba.se.spi.transport.CorbaContactInfoListIterator;

import com.sun.corba.se.impl.corba.RequestImpl;
import com.sun.corba.se.impl.protocol.CorbaInvocationInfo;
import com.sun.corba.se.impl.transport.CorbaContactInfoListImpl;
import com.sun.corba.se.impl.util.JDKBridge;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;

// implements com.sun.corba.se.impl.core.ClientRequestDispatcher
// so RMI-IIOP Util.isLocal can call ClientRequestDispatcher.useLocalInvocation.

/**
 * @author Harold Carr
 */
public class CorbaClientDelegateImpl extends CorbaClientDelegate {

  private ORB orb;
  private ORBUtilSystemException wrapper;

  private CorbaContactInfoList contactInfoList;

  public CorbaClientDelegateImpl(ORB orb,
      CorbaContactInfoList contactInfoList) {
    this.orb = orb;
    this.wrapper = ORBUtilSystemException.get(orb,
        CORBALogDomains.RPC_PROTOCOL);
    this.contactInfoList = contactInfoList;
  }

  //
  // framework.subcontract.Delegate
  //

  public Broker getBroker() {
    return orb;
  }

  public ContactInfoList getContactInfoList() {
    return contactInfoList;
  }

  //
  // CORBA_2_3.portable.Delegate
  //

  public OutputStream request(org.omg.CORBA.Object self,
      String operation,
      boolean responseExpected) {
    ClientInvocationInfo invocationInfo =
        orb.createOrIncrementInvocationInfo();
    Iterator contactInfoListIterator =
        invocationInfo.getContactInfoListIterator();
    if (contactInfoListIterator == null) {
      contactInfoListIterator = contactInfoList.iterator();
      invocationInfo.setContactInfoListIterator(contactInfoListIterator);
    }
    if (!contactInfoListIterator.hasNext()) {
      throw ((CorbaContactInfoListIterator) contactInfoListIterator)
          .getFailureException();
    }
    CorbaContactInfo contactInfo = (CorbaContactInfo) contactInfoListIterator.next();
    ClientRequestDispatcher subcontract = contactInfo.getClientRequestDispatcher();
    // Remember chosen subcontract for invoke and releaseReply.
    // NOTE: This is necessary since a stream is not available in
    // releaseReply if there is a client marshaling error or an
    // error in _invoke.
    invocationInfo.setClientRequestDispatcher(subcontract);
    return (OutputStream)
        subcontract.beginRequest(self, operation,
            !responseExpected, contactInfo);
  }

  public InputStream invoke(org.omg.CORBA.Object self, OutputStream output)
      throws
      ApplicationException,
      RemarshalException {
    ClientRequestDispatcher subcontract = getClientRequestDispatcher();
    return (InputStream)
        subcontract.marshalingComplete((Object) self, (OutputObject) output);
  }

  public void releaseReply(org.omg.CORBA.Object self, InputStream input) {
    // NOTE: InputStream may be null (e.g., exception request from PI).
    ClientRequestDispatcher subcontract = getClientRequestDispatcher();
    subcontract.endRequest(orb, self, (InputObject) input);
    orb.releaseOrDecrementInvocationInfo();
  }

  private ClientRequestDispatcher getClientRequestDispatcher() {
    return (ClientRequestDispatcher)
        ((CorbaInvocationInfo) orb.getInvocationInfo())
            .getClientRequestDispatcher();
  }

  public org.omg.CORBA.Object get_interface_def(org.omg.CORBA.Object obj) {
    InputStream is = null;
    // instantiate the stub
    org.omg.CORBA.Object stub = null;

    try {
      OutputStream os = request(null, "_interface", true);
      is = (InputStream) invoke((org.omg.CORBA.Object) null, os);

      org.omg.CORBA.Object objimpl =
          (org.omg.CORBA.Object) is.read_Object();

      // check if returned object is of correct type
      if (!objimpl._is_a("IDL:omg.org/CORBA/InterfaceDef:1.0")) {
        throw wrapper.wrongInterfaceDef(CompletionStatus.COMPLETED_MAYBE);
      }

      try {
        stub = (org.omg.CORBA.Object)
            JDKBridge.loadClass("org.omg.CORBA._InterfaceDefStub").
                newInstance();
      } catch (Exception ex) {
        throw wrapper.noInterfaceDefStub(ex);
      }

      org.omg.CORBA.portable.Delegate del =
          StubAdapter.getDelegate(objimpl);
      StubAdapter.setDelegate(stub, del);
    } catch (ApplicationException e) {
      // This cannot happen.
      throw wrapper.applicationExceptionInSpecialMethod(e);
    } catch (RemarshalException e) {
      return get_interface_def(obj);
    } finally {
      releaseReply((org.omg.CORBA.Object) null, (InputStream) is);
    }

    return stub;
  }

  public boolean is_a(org.omg.CORBA.Object obj, String dest) {
    // dest is the typeId of the interface to compare against.
    // repositoryIds is the list of typeIds that the stub knows about.

    // First we look for an answer using local information.

    String[] repositoryIds = StubAdapter.getTypeIds(obj);
    String myid = contactInfoList.getTargetIOR().getTypeId();
    if (dest.equals(myid)) {
      return true;
    }
    for (int i = 0; i < repositoryIds.length; i++) {
      if (dest.equals(repositoryIds[i])) {
        return true;
      }
    }

    // But repositoryIds may not be complete, so it may be necessary to
    // go to server.

    InputStream is = null;
    try {
      OutputStream os = request(null, "_is_a", true);
      os.write_string(dest);
      is = (InputStream) invoke((org.omg.CORBA.Object) null, os);

      return is.read_boolean();

    } catch (ApplicationException e) {
      // This cannot happen.
      throw wrapper.applicationExceptionInSpecialMethod(e);
    } catch (RemarshalException e) {
      return is_a(obj, dest);
    } finally {
      releaseReply((org.omg.CORBA.Object) null, (InputStream) is);
    }
  }

  public boolean non_existent(org.omg.CORBA.Object obj) {
    InputStream is = null;
    try {
      OutputStream os = request(null, "_non_existent", true);
      is = (InputStream) invoke((org.omg.CORBA.Object) null, os);

      return is.read_boolean();

    } catch (ApplicationException e) {
      // This cannot happen.
      throw wrapper.applicationExceptionInSpecialMethod(e);
    } catch (RemarshalException e) {
      return non_existent(obj);
    } finally {
      releaseReply((org.omg.CORBA.Object) null, (InputStream) is);
    }
  }

  public org.omg.CORBA.Object duplicate(org.omg.CORBA.Object obj) {
    return obj;
  }

  public void release(org.omg.CORBA.Object obj) {
    // DO NOT clear out internal variables to release memory
    // This delegate may be pointed-to by other objrefs.
  }

  // obj._get_delegate() == this due to the argument passing conventions in
  // portable.ObjectImpl, so we just ignore obj here.
  public boolean is_equivalent(org.omg.CORBA.Object obj,
      org.omg.CORBA.Object ref) {
    if (ref == null) {
      return false;
    }

    // If ref is a local object, it is not a Stub!
    if (!StubAdapter.isStub(ref)) {
      return false;
    }

    Delegate del = StubAdapter.getDelegate(ref);
    if (del == null) {
      return false;
    }

    // Optimize the x.is_equivalent( x ) case
    if (del == this) {
      return true;
    }

    // If delegate was created by a different ORB, return false
    if (!(del instanceof CorbaClientDelegateImpl)) {
      return false;
    }

    CorbaClientDelegateImpl corbaDelegate = (CorbaClientDelegateImpl) del;
    CorbaContactInfoList ccil =
        (CorbaContactInfoList) corbaDelegate.getContactInfoList();
    return this.contactInfoList.getTargetIOR().isEquivalent(
        ccil.getTargetIOR());
  }

  /**
   * This method overrides the org.omg.CORBA.portable.Delegate.equals method,
   * and does the equality check based on IOR equality.
   */
  public boolean equals(org.omg.CORBA.Object self, java.lang.Object other) {
    if (other == null) {
      return false;
    }

    if (!StubAdapter.isStub(other)) {
      return false;
    }

    Delegate delegate = StubAdapter.getDelegate(other);
    if (delegate == null) {
      return false;
    }

    if (delegate instanceof CorbaClientDelegateImpl) {
      CorbaClientDelegateImpl otherDel = (CorbaClientDelegateImpl)
          delegate;
      IOR otherIor = otherDel.contactInfoList.getTargetIOR();
      return this.contactInfoList.getTargetIOR().equals(otherIor);
    }

    // Come here if other is not implemented by our ORB.
    return false;
  }

  public int hashCode(org.omg.CORBA.Object obj) {
    return this.hashCode();
  }

  public int hash(org.omg.CORBA.Object obj, int maximum) {
    int h = this.hashCode();
    if (h > maximum) {
      return 0;
    }
    return h;
  }

  public Request request(org.omg.CORBA.Object obj, String operation) {
    return new RequestImpl(orb, obj, null, operation, null, null, null,
        null);
  }

  public Request create_request(org.omg.CORBA.Object obj,
      Context ctx,
      String operation,
      NVList arg_list,
      NamedValue result) {
    return new RequestImpl(orb, obj, ctx, operation, arg_list,
        result, null, null);
  }

  public Request create_request(org.omg.CORBA.Object obj,
      Context ctx,
      String operation,
      NVList arg_list,
      NamedValue result,
      ExceptionList exclist,
      ContextList ctxlist) {
    return new RequestImpl(orb, obj, ctx, operation, arg_list, result,
        exclist, ctxlist);
  }

  public org.omg.CORBA.ORB orb(org.omg.CORBA.Object obj) {
    return this.orb;
  }

  /**
   * Returns true if this object is implemented by a local servant.
   *
   * REVISIT: locatedIOR should be replaced with a method call that
   * returns the current IOR for this request (e.g. ContactInfoChooser).
   *
   * @param self The object reference which delegated to this delegate.
   * @return true only if the servant incarnating this object is located in this ORB.
   */
  public boolean is_local(org.omg.CORBA.Object self) {
    // XXX this need to check isNextCallValid
    return contactInfoList.getEffectiveTargetIOR().getProfile().
        isLocal();
  }

  public ServantObject servant_preinvoke(org.omg.CORBA.Object self,
      String operation,
      Class expectedType) {
    return
        contactInfoList.getLocalClientRequestDispatcher()
            .servant_preinvoke(self, operation, expectedType);
  }

  public void servant_postinvoke(org.omg.CORBA.Object self,
      ServantObject servant) {
    contactInfoList.getLocalClientRequestDispatcher()
        .servant_postinvoke(self, servant);
  }

  // XXX Should this be public?
    /* Returns the codebase for object reference provided.
     * @param self the object reference whose codebase needs to be returned.
     * @return the codebase as a space delimited list of url strings or
     * null if none.
     */
  public String get_codebase(org.omg.CORBA.Object self) {
    if (contactInfoList.getTargetIOR() != null) {
      return contactInfoList.getTargetIOR().getProfile().getCodebase();
    }
    return null;
  }

  public String toString(org.omg.CORBA.Object self) {
    return contactInfoList.getTargetIOR().stringify();
  }

  ////////////////////////////////////////////////////
  //
  // java.lang.Object
  //

  public int hashCode() {
    return this.contactInfoList.hashCode();
  }
}

// End of file.
